import 'dart:convert';

import 'package:backend.core/core/domain/config/config.dart';
import 'package:backend.core/logger/logger.dart';
import 'package:backend.purchases/domain/clients/app_store_api_client.dart';
import 'package:backend.purchases/domain/entities/app_store_api/app_store_api_purchased_subscription_renewal_info.dart';
import 'package:backend.purchases/domain/entities/app_store_api/app_store_api_transaction.dart';
import 'package:backend.purchases/domain/repositories/app_store_api_purchased_item_repository/repository.dart';
import 'package:backend.purchases/infrastructure/dtos/app_store_api/app_store_api_renewal_info_dto.dart';
import 'package:backend.purchases/infrastructure/dtos/app_store_api/app_store_api_transaction_dto_factory.dart';
import 'package:built_collection/built_collection.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:http/http.dart' as http;

part 'dtos/transaction_history_getting_successful_response_body_dto.dart';

class AppStoreApiPurchasedItemRepositoryImpl implements AppStoreApiPurchasedItemRepository {
  const AppStoreApiPurchasedItemRepositoryImpl({
    required http.Client httpClient,
    required AppStoreApiClient appStoreApiClient,
  })  : _httpClient = httpClient,
        _appStoreApiClient = appStoreApiClient;

  final http.Client _httpClient;
  final AppStoreApiClient _appStoreApiClient;

  @override
  Future<Either<AppStoreApiOriginalTransactionIdGettingFailure, String>> getOriginalTransactionId({
    required String receipt,
  }) async {
    const kProductionUrlString = 'https://buy.itunes.apple.com/verifyReceipt';
    final productionUrl = Uri.parse(kProductionUrlString);

    final payload = {
      'receipt-data': receipt,
      'password': Config().appStoreSharedSecret,
      'exclude-old-transactions': true,
    };

    final requestBody = jsonEncode(payload);

    final headers = {
      'Content-Type': 'application/json',
    };

    http.Response response;

    try {
      response = await _httpClient.post(
        productionUrl,
        body: requestBody,
        headers: headers,
      );
    } catch (e, s) {
      Logger().w(null, e, s);
      const failure = NetworkAppStoreApiOriginalTransactionIdGettingFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      Logger().w(null, response.body, StackTrace.current);
      const failure = AppStoreServerAppStoreApiOriginalTransactionIdGettingFailure();
      return left(failure);
    }

    var responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    var appStoreApiStatus = responseBody['status'] as int;

    if (appStoreApiStatus == 21007) {
      const kDevelopmentUrlString = 'https://sandbox.itunes.apple.com/verifyReceipt';
      final developmentUrl = Uri.parse(kDevelopmentUrlString);

      try {
        response = await _httpClient.post(
          developmentUrl,
          body: requestBody,
          headers: headers,
        );
      } catch (e, s) {
        Logger().w(null, e, s);
        const failure = NetworkAppStoreApiOriginalTransactionIdGettingFailure();
        return left(failure);
      }

      if (response.statusCode != 200) {
        Logger().w(null, response.body, StackTrace.current);
        const failure = AppStoreServerAppStoreApiOriginalTransactionIdGettingFailure();
        return left(failure);
      }

      responseBody = jsonDecode(response.body) as Map<String, dynamic>;
      appStoreApiStatus = responseBody['status'] as int;
    }

    if (appStoreApiStatus != 0) {
      return left(const AppStoreServerAppStoreApiOriginalTransactionIdGettingFailure());
    }

    final receiptJson = responseBody['receipt'] as Map<String, dynamic>;

    final purchasedItemMaps = List.castFrom<dynamic, Map>(receiptJson['in_app']);

    final firstPurchasedItemJson =
        Map.castFrom<dynamic, dynamic, String, dynamic>(purchasedItemMaps.first);

    final originalTransactionId = firstPurchasedItemJson['original_transaction_id'] as String;

    return right(originalTransactionId);
  }

  @override
  Future<Either<AppStoreApiTransactionHistoryGettingFailure, BuiltList<AppStoreApiTransaction>>>
      getTransactionHistory({
    required String originalTransactionId,
  }) async {
    const kUrlScheme = 'https';
    const kUrlProductionHost = 'api.storekit.itunes.apple.com';
    const kUrlDevelopmentHost = 'api.storekit-sandbox.itunes.apple.com';

    final urlPath = 'inApps/v1/history/$originalTransactionId';

    http.Response response;

    final productionUrl = Uri(
      scheme: kUrlScheme,
      host: kUrlProductionHost,
      path: urlPath,
    );

    var effectiveUrlHost = kUrlProductionHost;

    try {
      response = await _appStoreApiClient.get(productionUrl);
    } catch (e, s) {
      Logger().w(null, e, s);
      const failure = NetworkAppStoreApiTransactionHistoryGettingFailure();
      return left(failure);
    }

    if (response.statusCode == 404) {
      final developmentUrl = Uri(
        scheme: kUrlScheme,
        host: kUrlDevelopmentHost,
        path: urlPath,
      );

      effectiveUrlHost = kUrlDevelopmentHost;

      try {
        response = await _appStoreApiClient.get(developmentUrl);
      } catch (e, s) {
        Logger().w(null, e, s);
        const failure = NetworkAppStoreApiTransactionHistoryGettingFailure();
        return left(failure);
      }
    }

    if (response.statusCode == 401) {
      return left(const UnauthorizedAppStoreApiTransactionHistoryGettingFailure());
    }

    if (response.statusCode != 200) {
      Logger().w(null, response.body, StackTrace.current);
      const failure = ServerAppStoreApiTransactionHistoryGettingFailure();
      return left(failure);
    }

    var responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    var responseBodyDto =
        _TransactionHistoryGettingSuccessfulResponseBodyDto.fromJson(responseBody);

    final signedTransactions = <String>[];
    signedTransactions.addAll(responseBodyDto.signedTransactions);

    while (responseBodyDto.hasMore) {
      final url = Uri(
        scheme: kUrlScheme,
        host: effectiveUrlHost,
        path: urlPath,
        queryParameters: {
          'revision': responseBodyDto.revisionToken,
        },
      );

      try {
        response = await _appStoreApiClient.get(url);
      } catch (e, s) {
        Logger().w(null, e, s);
        const failure = NetworkAppStoreApiTransactionHistoryGettingFailure();
        return left(failure);
      }

      if (response.statusCode == 401) {
        return left(const UnauthorizedAppStoreApiTransactionHistoryGettingFailure());
      }

      if (response.statusCode != 200) {
        Logger().w(null, response.body, StackTrace.current);
        const failure = ServerAppStoreApiTransactionHistoryGettingFailure();
        return left(failure);
      }

      responseBody = jsonDecode(response.body) as Map<String, dynamic>;
      responseBodyDto = _TransactionHistoryGettingSuccessfulResponseBodyDto.fromJson(responseBody);

      signedTransactions.addAll(responseBodyDto.signedTransactions);
    }

    final transactions = signedTransactions.map((signedTransaction) {
      final transactionDto = AppStoreApiTransactionDtoFactory().fromJws(signedTransaction);
      return transactionDto.toEntity();
    }).toBuiltList();

    return right(transactions);
  }

  @override
  Future<
      Either<AppStoreApiPurchasedSubscriptionRenewalInfoGettingFailure,
          BuiltList<AppStoreApiPurchasedSubscriptionRenewalInfo>>> getRenewalInfos({
    required String originalTransactionId,
  }) async {
    const kUrlScheme = 'https';
    const kUrlProductionHost = 'api.storekit.itunes.apple.com';
    const kUrlDevelopmentHost = 'api.storekit-sandbox.itunes.apple.com';

    final urlPath = 'inApps/v1/subscriptions/$originalTransactionId';

    http.Response response;

    final productionUrl = Uri(
      scheme: kUrlScheme,
      host: kUrlProductionHost,
      path: urlPath,
    );

    try {
      response = await _appStoreApiClient.get(productionUrl);
    } catch (e, s) {
      Logger().w(null, e, s);
      const failure = NetworkAppStoreApiPurchasedSubscriptionRenewalInfoGettingFailure();
      return left(failure);
    }

    if (response.statusCode == 404) {
      final developmentUrl = Uri(
        scheme: kUrlScheme,
        host: kUrlDevelopmentHost,
        path: urlPath,
      );

      try {
        response = await _appStoreApiClient.get(developmentUrl);
      } catch (e, s) {
        Logger().w(null, e, s);
        const failure = NetworkAppStoreApiPurchasedSubscriptionRenewalInfoGettingFailure();
        return left(failure);
      }
    }

    if (response.statusCode == 401) {
      return left(const UnauthorizedAppStoreApiPurchasedSubscriptionRenewalInfoGettingFailure());
    }

    if (response.statusCode != 200) {
      Logger().w(null, response.body, StackTrace.current);
      const failure = ServerAppStoreApiPurchasedSubscriptionRenewalInfoGettingFailure();
      return left(failure);
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;

    final subscriptionGroupIdentifierItemMaps = List.castFrom<dynamic, Map>(responseBody['data']);
    final lastTransactionItemJsons = <Map<String, dynamic>>[];

    for (final subscriptionGroupIdentifierItemMap in subscriptionGroupIdentifierItemMaps) {
      final lastTransactionItemMaps =
          List.castFrom<dynamic, Map>(subscriptionGroupIdentifierItemMap['lastTransactions']);

      for (final lastTransactionItemMap in lastTransactionItemMaps) {
        final lastTransactionItemJson =
            Map.castFrom<dynamic, dynamic, String, dynamic>(lastTransactionItemMap);
        lastTransactionItemJsons.add(lastTransactionItemJson);
      }
    }

    final renewalInfos = lastTransactionItemJsons.map((lastTransactionItemJson) {
      final signedRenewalInfo = lastTransactionItemJson['signedRenewalInfo'] as String;
      final renewalInfoDto =
          AppStoreApiPurchasedSubscriptionRenewalInfoDto.fromJws(signedRenewalInfo);
      return renewalInfoDto.toEntity();
    }).toBuiltList();

    return right(renewalInfos);
  }
}
